// HSS.Data.DataRowReader.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license
// http://www.opensource.org/licenses/mit-license.html
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       DataRowReader.cs
// Author:     HSS\gbanta
// Created:    08/12/2010
// Modified:   12/06/2010
// ----------------------------------------------------------------------------
namespace HSS.Data
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	using System.Data;
	using System.Data.Common;
	using System.IO;
	using System.Xml;


	#endregion

	#region DataRowReader
	/// <summary>
	/// Provides read only, forward only, access to rows of data. This object is 
	/// similar to the <see cref="DbDataReader"/>, except there is no connection
	/// to a Database.
	/// <para>
	/// The DataRowReader supports 
	/// parsing data from a <see cref="DbDataReader"/>, an <see cref="XmlNodeList"/>
	/// or a <see cref="TextReader"/> and allows manually adding additional rows 
	/// of data using the <c>AddRow</c> methods.
	/// </para>
	/// </summary>
	public sealed class DataRowReader : IDataReader
	{
		#region Fields
		Dictionary<int, Metadata> metaData;
		Dictionary<string, int> fields;
		Dictionary<int, byte[]> buffers;
		Queue<object[]> rows;
		object[] currentRow;
		bool initialRead = false;
		#endregion

		#region Constructor
		/// <summary>
		/// Default ctor
		/// </summary>
		public DataRowReader()
		{
			_closed = true;
		}
		/// <summary>
		///  Reads all rows from the DbDataReader closing and setting to null when done
		/// </summary>
		/// <param name="dr">The DbDataReader to parse</param>
		public DataRowReader(System.Data.Common.DbDataReader dr)
		{
			_closed = true;
			if (null != dr)
			{
				if (dr.Read())
				{
					this.rows = new Queue<object[]>();
					this.fields = new Dictionary<string, int>();
					this.metaData = new Dictionary<int, Metadata>();
					var row = new object[dr.FieldCount];

					// Read the field names and the first row
					int fieldCount = dr.FieldCount;
					for (int i = 0; i < fieldCount; i++)
					{
						var name = dr.GetName(i);
						var fieldType = dr.GetFieldType(i);
						this.fields.Add(name, i);
						this.metaData.Add(i, new Metadata(i, name, fieldType, fieldType.FullName));
						row[i] = dr[i];
					}
					this.rows.Enqueue(row);

					// Read the rest of the rows if any
					while (dr.Read())
					{
						row = new object[fieldCount];
						dr.GetValues(row);
						this.rows.Enqueue(row);
					}
					this._closed = false;
				}
				dr.Dispose();
				dr = null;
			}
		}
		/// <summary>
		/// Reads each node in the List as a Row of data and each ChildNode as a field within the row.
		/// The ChildNode Name represents a field name and the
		/// the ChildNode InnerText represents a field value
		/// </summary>
		/// <param name="nl">The XmlNodeList to parse</param>
		public DataRowReader(XmlNodeList nl)
		{
			_closed = true;
			if (null != nl && nl.Count > 0)
			{
				int rowIndex = 0;

				// Must have child nodes (fields)
				XmlNode xn = nl[rowIndex];
				if (xn.ChildNodes.Count == 0)
					return;

				// Read the field names and the first row
				this.rows = new Queue<object[]>();
				this.fields = new Dictionary<string, int>();
				this.metaData = new Dictionary<int, Metadata>();
				object[] row = new object[xn.ChildNodes.Count];
				for (int i = 0; i < xn.ChildNodes.Count; i++)
				{
					XmlNode xc = xn.ChildNodes[i];
					this.fields.Add(xc.Name, i);
					this.metaData.Add(i, new Metadata(i, xc.Name, typeof(string), typeof(string).FullName));
					row[i] = xc.InnerText;
				}
				this.rows.Enqueue(row);
				rowIndex++;

				// Read the rest of the rows if any
				for (; rowIndex < nl.Count; rowIndex++)
				{
					xn = nl[rowIndex];
					row = new object[xn.ChildNodes.Count];
					for (int i = 0; i < xn.ChildNodes.Count; i++)
					{
						row[i] = xn.ChildNodes[i].InnerText;
					}
					this.rows.Enqueue(row);
				}
				this._closed = false;
			}
		}
		/// <summary>
		/// Reads all lines as the rows of data.
		/// </summary>
		/// <param name="reader">The TextReader to parse.</param>
		/// <param name="delimeter">The delimeter to parse column names and values.</param>
		/// <param name="columnNames">A string containing the column names seperated by the parameter passed in the <paramref name="delimeter"/> parameter.</param>
		/// <param name="columnCount">The number of columns to parse.</param>
		public DataRowReader(TextReader reader, string delimeter, string columnNames, int columnCount)
		{
			_closed = true;
			if (null != reader)
			{
				try
				{
					this.rows = new Queue<object[]>();
					this.fields = new Dictionary<string, int>();
					this.metaData = new Dictionary<int, Metadata>();
					var fields = columnNames.Split(delimeter.ToCharArray(), columnCount);
					for (int i = 0; i < fields.Length; i++)
					{
						this.fields.Add(fields[i], i);
						this.metaData.Add(i, new Metadata(i, fields[i], typeof(string), typeof(string).FullName));
					}
					string line;
					while ((line = reader.ReadLine()) != null)
					{
						var rowValues = line.Split(delimeter.ToCharArray(), columnCount);
						rows.Enqueue(rowValues);
						_closed = false;
					}
				}
				finally
				{
					reader.Close();
					reader = null;
				}
			}
		}
		/// <summary>
		/// Reads all lines as the rows of data.
		/// </summary>
		/// <param name="reader">The TextReader to parse.</param>
		/// <param name="delimeter">The delimeter to parse column names and values.</param>
		/// <param name="columnNames">A string containing the column names seperated by the parameter passed in the <paramref name="delimeter"/> parameter.</param>
		public DataRowReader(TextReader reader, string delimeter, string columnNames)
		{
			_closed = true;
			if (null != reader)
			{
				try
				{
					this.rows = new Queue<object[]>();
					this.fields = new Dictionary<string, int>();
					this.metaData = new Dictionary<int, Metadata>();
					var fields = columnNames.Split(delimeter.ToCharArray());
					for (int i = 0; i < fields.Length; i++)
					{
						this.fields.Add(fields[i], i);
						this.metaData.Add(i, new Metadata(i, fields[i], typeof(string), typeof(string).FullName));
					}
					string line;
					while ((line = reader.ReadLine()) != null)
					{
						var rowValues = line.Split(delimeter.ToCharArray(), fields.Length);
						rows.Enqueue(rowValues);
						_closed = false;
					}
				}
				finally
				{
					reader.Close();
					reader = null;
				}
			}
		}
		/// <summary>
		/// Reads all lines as the rows of data.
		/// </summary>
		/// <param name="reader">The TextReader to parse.</param>
		/// <param name="delimeter">The delimeter to parse column names and values.</param>
		/// <param name="columnNames">A string array containing the column names.</param>
		public DataRowReader(TextReader reader, string delimeter, string[] columnNames)
		{
			_closed = true;
			if (null != reader)
			{
				try
				{
					this.rows = new Queue<object[]>();
					this.fields = new Dictionary<string, int>();
					this.metaData = new Dictionary<int, Metadata>();
					for (int i = 0; i < columnNames.Length; i++)
					{
						this.fields.Add(columnNames[i], i);
						this.metaData.Add(i, new Metadata(i, columnNames[i], typeof(string), typeof(string).FullName));
					}
					string line;
					while ((line = reader.ReadLine()) != null)
					{
						var rowValues = line.Split(delimeter.ToCharArray(), columnNames.Length);
						rows.Enqueue(rowValues);
						_closed = false;
					}
				}
				finally
				{
					reader.Close();
					reader = null;
				}
			}
		}
		/// <summary>
		/// Reads the first line as the column headers, and then the rest of the lines as the rows of data.
		/// </summary>
		/// <param name="reader">The TextReader to parse.</param>
		/// <param name="delimeter">The delimeter to parse column Name and values.</param>
		public DataRowReader(TextReader reader, string delimeter)
		{
			_closed = true;
			if (null != reader)
			{
				try
				{
					this.rows = new Queue<object[]>();
					this.fields = new Dictionary<string, int>();
					this.metaData = new Dictionary<int, Metadata>();

					string[] columnNames;
					int columnCount = 0;
					bool firstRow = true;
					string line;
					while ((line = reader.ReadLine()) != null)
					{
						if (firstRow)
						{
							// Read column headers.
							firstRow = false;
							columnNames = line.Split(delimeter.ToCharArray());
							columnCount = columnNames.Length;
							for (int i = 0; i < columnCount; i++)
							{
								this.fields.Add(columnNames[i], i);
								this.metaData.Add(i, new Metadata(i, columnNames[i], typeof(string), typeof(string).FullName));
							}
							continue;
						}

						var rowValues = line.Split(delimeter.ToCharArray(), columnCount);
						rows.Enqueue(rowValues);
						_closed = false;
					}
				}
				finally
				{
					reader.Close();
					reader = null;
				}
			}
		}
		#endregion

		#region Properties
		/// <summary>
		/// Gets a value indicating whether the DataRowReader contains one or more rows.
		/// </summary>
		/// <value>true if the DataRowReader contains one or more rows; otherwise false.</value>
		public bool HasRows
		{
			get
			{
				this.ValidateClosed();

				if (null != rows)
					return (rows.Count > 0);
				else
					return false;
			}
		}
		/// <summary>
		/// Gets or sets the ValidateNull flag.
		/// </summary>
		/// <remarks>
		/// <para>The default value is <c>True</c></para>
		/// <para>
		/// When the value is true, will cause an exception
		/// to be thrown when attempting to get a null value.
		/// </para>
		/// </remarks>
		public bool ValidateNull
		{
			get { return _validateNull; }
			set { _validateNull = value; }
		} private bool _validateNull = true;
		/// <summary>
		/// Gets or sets the ValidateType flag.
		/// </summary>
		/// <remarks>
		/// <para>The default value is <c>True</c></para>
		/// <para>
		/// When the value is true, will cause an exception
		/// to be thrown when attempting to get a value that
		/// is not already the correct type or is not a string.
		/// </para>
		/// </remarks>
		public bool ValidateType
		{
			get { return _validateType; }
			set { _validateType = value; }
		} private bool _validateType = true;
		/// <summary>
		/// Gets or sets the ValidateEmptyString flag.
		/// </summary>
		/// <remarks>
		/// <para>The default value is <c>True</c></para>
		/// <para>
		/// When the value is true, will cause an exception
		/// to be thrown when attempting to get a value that
		/// is an empty string.
		/// </para>
		/// </remarks>
		public bool ValidateEmptyString
		{
			get { return _validateEmptyString; }
			set { _validateEmptyString = value; }
		} private bool _validateEmptyString = true;
		#endregion

		#region Methods

		/// <summary>
		/// Adds one row of data to the DataRowReader
		/// </summary>
		/// <param name="values">The array of objects to add</param>
		/// <param name="columnNames">The array of object field names</param>
		/// <param name="reset">True to clear out the existing rows of data. Does not clear the ColumnNames</param>
		/// <returns>True if addition was successful</returns>
		public bool AddRow(object[] values, string[] columnNames, bool reset)
		{
			if (values.Length != columnNames.Length)
				throw new ArgumentException("The values and columnNames arrays must have the same number of members", "values");

			if (null == this.fields && null == columnNames)
				throw new ArgumentException("When adding the first row, the columns are required.", "columnNames");

			if (null == rows)
				rows = new Queue<object[]>();
			else
			{
				if (reset)
					rows.Clear();
			}
			if (null == this.fields && null != columnNames)
			{
				this.fields = new Dictionary<string, int>();
				this.metaData = new Dictionary<int, Metadata>();
				for (int i = 0; i < columnNames.Length; i++)
				{
					this.fields.Add(columnNames[i], i);
					var valType = values[i].GetType();
					this.metaData.Add(i, new Metadata(i, columnNames[i], valType, valType.FullName));
				}
			}
			rows.Enqueue(values);
			_closed = false;
			return true;
		}
		/// <summary>
		/// Adds one row of data to the DataRowReader
		/// </summary>
		/// <param name="values">The array of objects to add</param>
		/// <param name="columnNames">The array of object field names</param>
		/// <returns>True if addition was successful</returns>
		public bool AddRow(object[] values, string[] columnNames)
		{
			return AddRow(values, columnNames, false);
		}
		/// <summary>
		/// Adds one row of data to the DataRowReader
		/// </summary>
		/// <param name="values">The array of objects to add</param>
		/// <returns>True if addition was successful</returns>
		public bool AddRow(object[] values)
		{
			return AddRow(values, null, false);
		}
		/// <summary>
		/// Get the Current Row of Data as an Object Array
		/// </summary>
		/// <returns>The current row as an Object Array</returns>
		public object[] GetRow()
		{
			ValidateRow();
			object[] objs = new object[this.currentRow.Length];
			this.currentRow.CopyTo(objs, 0);

			return objs;
		}
		/// <summary>
		/// Get the Current Row of Data as a String Array
		/// </summary>
		/// <param name="Trim">If true, will call Trim on the string</param>
		/// <returns>The current row as a string Array</returns>
		public string[] GetRow(bool Trim)
		{
			ValidateRow();
			object[] objs = this.GetRow();
			string[] strings = new string[objs.Length];
			for (int i = 0; i < objs.Length; i++)
			{
				if (Trim)
					strings[i] = objs[i].ToString().Trim();
				else
					strings[i] = objs[i].ToString();
			}

			return strings;
		}

		/// <summary>
		/// Attempts to retrieve the ordinal of a named field.
		/// </summary>
		/// <param name="name">The name of the field to find.</param>
		/// <param name="ordinal">The ordinal index if a field was found by the name requested.</param>
		/// <returns>true if a named field was found; otherwise false.</returns>
		public bool TryGetOrdinal(string name, out int ordinal)
		{
			this.ValidateClosed();
			this.ValidateFields();
			return fields.TryGetValue(name, out ordinal);
		}

		private void ValidateClosed()
		{
			if (this._closed)
				throw new InvalidOperationException("Invalid attempt to access when reader is closed.");
		}
		private void ValidateValues()
		{
			if (null == this.currentRow)
				throw new InvalidOperationException("Invalid attempt to read when no data is present.");
		}
		private void ValidateFields()
		{
			if (null == this.fields || this.fields.Count == 0)
				throw new InvalidOperationException("Invalid attempt to read when no data is present.");
		}

		private bool TryGetValue(int i, out object value)
		{
			value = this.GetValue(i);
			return (DBNull.Value != value && null != value);
		}
		private object GetValueOrThrowIfNull(int i)
		{
			object val;
			if (!TryGetValue(i, out val))
			{
				if (this.ValidateNull)
					throw new NullReferenceException();
				else
					return new object();
			}
			return val;
		}

		#endregion

		#region IDataReader Members

		/// <summary>
		/// Returns the number of records returned from the DataReader
		/// </summary>
		public int RecordsAffected
		{
			get
			{
				if (null != rows)
					return rows.Count;
				else
					return 0;
			}
		}

		/// <summary>
		/// Is the DataRowReader Closed
		/// </summary>
		public bool IsClosed
		{
			get { return _closed; }
		} bool _closed;

		/// <summary>
		/// Not implemented
		/// </summary>
		/// <returns>false</returns>
		public bool NextResult()
		{
			return false;
		}

		/// <summary>
		/// Closes the Data Row Reader and clears the underlying cache.
		/// </summary>
		public void Close()
		{
			_closed = true;
			if (null != rows)
			{
				rows.Clear();
				rows = null;
			}
		}

		/// <summary>
		/// Reads the next row of data, if available.
		/// </summary>
		/// <returns>False if the no more rows.</returns>
		/// <exception cref="ObjectDisposedException">If the DataRowReader has been closed or disposed</exception>
		public bool Read()
		{
			if (!initialRead)
			{
				initialRead = true;
				if (this._closed)
					return false;
			}
			else
				this.ValidateClosed();

			if (this.rows.Count > 0)
			{
				try
				{
					this.currentRow = rows.Dequeue();
					if (null == this.buffers)
						this.buffers = new Dictionary<int, byte[]>();
					else
						this.buffers.Clear();
				}
				catch { this.currentRow = null; }
				return (null != this.currentRow);
			}

			return false;
		}

		/// <summary>
		/// Not implemented
		/// </summary>
		public int Depth
		{
			get { return 0; }
		}

		/// <summary>
		/// Not implemented
		/// </summary>
		/// <returns>null</returns>
		public DataTable GetSchemaTable()
		{
			return null;
		}

		#endregion

		#region IDisposable Members
		/// <summary>
		/// Disposes the underlying cache.
		/// </summary>
		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}
		private void Dispose(bool disposing)
		{
			if (_closed)
				return;

			if (disposing)
				this.Close();
		}
		#endregion

		#region IDataRecord Members

		private void ValidateRow()
		{
			this.ValidateClosed();
			this.ValidateValues();
		}
		private void ValidateOridinal(int i)
		{
			if (i < 0 || i > this.FieldCount)
				throw new IndexOutOfRangeException("The index passed was outside the range of 0 through FieldCount.");
		}

		/// <summary>
		/// Gets the number of fields in the current row.
		/// </summary>
		public int FieldCount
		{
			get
			{
				this.ValidateClosed();
				if (null != fields)
					return fields.Count;
				else
					return 0;
			}
		}

		/// <summary>
		/// Gets the value at the specified field index from the current row.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The value at the specified field index from the current row.</returns>
		public object this[int i]
		{
			get
			{
				return this.GetValue(i);
			}
		}

		/// <summary>
		/// Gets the value at the specified field name from the current row.
		/// </summary>
		/// <param name="name">The name of the field to find.</param>
		/// <returns>The value at the specified field name from the current row.</returns>
		public object this[string name]
		{
			get
			{
				int index = this.GetOrdinal(name);
				return this.GetValue(index);
			}
		}

		/// <summary>
		/// Gets the value of the specified field as a Boolean.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The value of the field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Boolean value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Boolean value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Boolean or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Boolean.</exception>
		public bool GetBoolean(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is bool)
				return (bool)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return bool.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Boolean.");
				return default(bool);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Boolean or String.");
			return default(bool);
		}

		/// <summary>
		/// Gets the 8-bit unsigned integer value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The 8-bit unsigned integer value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Byte value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Byte value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Byte or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Byte.</exception>
		public byte GetByte(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is byte)
				return (byte)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return byte.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Byte.");
				return default(byte);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Byte or String.");
			return default(byte);
		}

		/// <summary>
		///  Reads a stream of bytes from the specified field offset into the buffer as an array, starting at the given buffer offset.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <param name="fieldOffset">The index within the field from which to start the read operation.</param>
		/// <param name="buffer">The buffer into which to read the stream of bytes.</param>
		/// <param name="bufferOffset">The index for buffer to start the read operation.</param>
		/// <param name="length">The number of bytes to read.</param>
		/// <returns>The actual number of bytes read.</returns>
		/// <remarks>Assumes the underlying object value is either a string or already a byte array. If the value is a string converts it to a byte array using UTF8 Encoding.</remarks>
		/// <exception cref="System.InvalidCastException">There is a type mismatch.</exception>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"></see>.</exception>
		/// <exception cref="System.NullReferenceException">The field specified by ordinal is null.</exception>
		public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length)
		{
			this.ValidateRow();
			this.ValidateOridinal(i);

			if (!this.buffers.ContainsKey(i))
			{
				byte[] bytes = null;
				object objValue = this.currentRow.GetValue(i);
				if (null == objValue)
					throw new NullReferenceException("The field specified by ordinal is null.");
				if (objValue is string)
				{
					var strValue = Convert.ToString(objValue);
					var strValueLen = System.Text.Encoding.UTF8.GetByteCount(strValue);
					bytes = new byte[strValueLen];
					System.Text.Encoding.UTF8.GetBytes(strValue, 0, strValue.Length, bytes, 0);
				}
				else if (objValue is byte[])
				{
					bytes = (byte[])objValue;
				}
				else
				{
					throw new InvalidCastException();
				}
				this.buffers.Add(i, bytes);
			}

			byte[] record = this.buffers[i];
			Buffer.BlockCopy(record, (int)fieldOffset, buffer, bufferOffset, length);

			return length;
		}

		/// <summary>
		/// Reads a stream of characters from the specified field offset into the buffer as an array, starting at the given buffer offset.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <param name="fieldoffset">The index within the row from which to start the read operation.</param>
		/// <param name="buffer">The buffer into which to read the stream of bytes.</param>
		/// <param name="bufferoffset">A 32-bit integer that represents the index in the buffer at which storing begins.</param>
		/// <param name="length">A 32-bit integer that represents the number of characters to get.</param>
		/// <returns>The number of characters retrieved.</returns>
		public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
		{
			var fieldDataChars = this.GetString(i).ToCharArray();
			var fieldDataCharsRead = 0L;

			int maxLen = fieldDataChars.Length;
			if (fieldoffset > 0x7fffffffL)
				throw new ArgumentOutOfRangeException(string.Format("Invalid source buffer (size of {0}) offset: {1}", new object[] { maxLen.ToString(System.Globalization.CultureInfo.InvariantCulture), fieldoffset.ToString(System.Globalization.CultureInfo.InvariantCulture) }), "fieldoffset");

			int sourceIndex = (int)fieldoffset;
			if (buffer == null)
				return (long)maxLen;
			if ((sourceIndex < 0) || (sourceIndex >= maxLen))
				return 0L;

			try
			{
				if (sourceIndex < maxLen)
				{
					if ((sourceIndex + length) > maxLen)
						maxLen -= sourceIndex;
					else
						maxLen = length;
				}
				Array.Copy(fieldDataChars, sourceIndex, buffer, bufferoffset, maxLen);
				fieldDataCharsRead += maxLen;
			}
			catch (Exception exception)
			{
				maxLen = fieldDataChars.Length;
				if (length < 0)
					throw new IndexOutOfRangeException(string.Format("length '{0}' is less than 0.", new object[] { length.ToString(System.Globalization.CultureInfo.InvariantCulture) }));
				if ((bufferoffset < 0) || (bufferoffset >= buffer.Length))
					throw new ArgumentOutOfRangeException(string.Format("Invalid destination buffer (size of {0}) offset: {1}", new object[] { maxLen.ToString(System.Globalization.CultureInfo.InvariantCulture), bufferoffset.ToString(System.Globalization.CultureInfo.InvariantCulture) }), "bufferoffset");
				if ((maxLen + bufferoffset) > buffer.Length)
					throw new IndexOutOfRangeException(string.Format("Buffer offset '{1}' plus the bytes available '{0}' is greater than the length of the passed in buffer.", new object[] { maxLen.ToString(System.Globalization.CultureInfo.InvariantCulture), bufferoffset.ToString(System.Globalization.CultureInfo.InvariantCulture) }));
				throw exception;
			}
			return maxLen;
		}

		/// <summary>
		/// Gets the data type information for the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The data type information for the specified field.</returns>
		public string GetDataTypeName(int i)
		{
			this.ValidateFields();
			Metadata metaDataInstance;
			if (this.metaData.TryGetValue(i, out metaDataInstance))
				return metaDataInstance.FielTypeName;
			else
				return string.Empty;
		}

		/// <summary>
		/// Gets the DateTime value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The DateTime value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default DateTime value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default DateTime value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type DateTime or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid DateTime.</exception>
		public DateTime GetDateTime(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is DateTime)
				return (DateTime)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return DateTime.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid DateTime.");
				return default(DateTime);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type DateTime or String.");
			return default(DateTime);
		}

		/// <summary>
		/// Gets the fixed-position numeric value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The fixed-position numeric value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Decimal value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Decimal value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Decimal or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Decimal.</exception>
		public decimal GetDecimal(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is decimal)
				return (decimal)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return decimal.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Decimal.");
				return default(decimal);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Decimal or String.");
			return default(decimal);
		}

		/// <summary>
		/// Gets the double-precision floating point number of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The double-precision floating point number of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Double value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Double value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Double or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Double.</exception>
		public double GetDouble(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is double)
				return (double)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return double.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Double.");
				return default(double);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Double or String.");
			return default(double);
		}

		/// <summary>
		/// Gets the Type information corresponding to the type of Object that would be returned from GetValue.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The Type information corresponding to the type of Object that would be returned from GetValue.</returns>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"></see>.</exception>
		public Type GetFieldType(int i)
		{
			var val = this.GetValue(i);
			if (null == val)
				return null;
			return val.GetType();
		}

		/// <summary>
		/// Gets the single-precision floating point number of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The single-precision floating point number of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Float value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Float value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Float or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Float.</exception>
		public float GetFloat(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is float)
				return (float)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return float.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Float.");
				return default(float);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Float or String.");
			return default(float);
		}

		/// <summary>
		/// Returns the GUID value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The GUID value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Guid value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Guid value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Guid or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Guid.</exception>
		public Guid GetGuid(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is Guid)
				return (Guid)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return Guid.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Guid.");
				return default(Guid);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Guid or String.");
			return default(Guid);
		}

		/// <summary>
		/// Gets the 16-bit signed integer value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The 16-bit signed integer value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Int16 value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Int16 value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Int16 or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Int16.</exception>
		public short GetInt16(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is short)
				return (short)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return short.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Int16.");
				return default(short);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Int16 or String.");
			return default(short);
		}

		/// <summary>
		/// Gets the 32-bit signed integer value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The 32-bit signed integer value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Int32 value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Int32 value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Int32 or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Int32.</exception>
		public int GetInt32(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is int)
				return (int)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return int.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Int32.");
				return default(int);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Int32 or String.");
			return default(int);
		}

		/// <summary>
		/// Gets the 64-bit signed integer value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The 64-bit signed integer value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type or a string that can be parsed to the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default Int64 value.
		/// </para>
		/// <para>
		/// To bypass Empty String validation (when underlying value is a string), set <see cref="ValidateEmptyString"/> to <c>false</c>. When false, will return the default Int64 value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type Int64 or String.</exception>
		/// <exception cref="System.InvalidOperationException">The value is an empty string and cannot be parsed into a valid Int64.</exception>
		public long GetInt64(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is long)
				return (long)val;
			if (val is string)
			{
				if (!string.IsNullOrEmpty((string)val))
					return long.Parse((string)val);
				if (this.ValidateEmptyString)
					throw new InvalidOperationException("The value is an empty string and cannot be parsed into a valid Int64.");
				return default(long);
			}
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type Int64 or String.");
			return default(long);
		}

		/// <summary>
		/// Gets the name of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The name of the field or an empty string if there is no value to return.</returns>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"></see>.</exception>
		public string GetName(int i)
		{
			this.ValidateRow();
			this.ValidateOridinal(i);
			Metadata metaDataInstance;
			if (this.metaData.TryGetValue(i, out metaDataInstance))
				return metaDataInstance.FieldName;
			return string.Empty;
		}

		/// <summary>
		/// Return the index of the named field.
		/// </summary>
		/// <param name="name">The name of the field to find.</param>
		/// <returns>The index of the named field.</returns>
		public int GetOrdinal(string name)
		{
			this.ValidateClosed();
			this.ValidateFields();
			return fields[name];
		}

		/// <summary>
		/// Gets the string value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The string value of the specified field.</returns>
		/// <remarks>
		/// <para>
		/// The data retrieved must already be of the requested type, or an exception is generated.
		/// </para>
		/// <para>
		/// Call <see cref="IsDBNull"/> to check for null values before calling this method or set <see cref="ValidateNull"/> to <c>false</c>.
		/// </para>
		/// <para>
		/// To bypass Type validation, set <see cref="ValidateType"/> to <c>false</c>. When false, will return the default String value.
		/// </para>
		/// </remarks>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"/>.</exception>
		/// <exception cref="System.NullReferenceException">The value 'null' was found where an instance of an object was required.</exception>
		/// <exception cref="System.InvalidCastException">The specified cast is not valid; the underlying value must be of type String.</exception>
		public string GetString(int i)
		{
			var val = this.GetValueOrThrowIfNull(i);
			if (val is string)
				return (string)val;
			if (this.ValidateType)
				throw new InvalidCastException("The specified cast is not valid; the underlying value must be of type String.");
			return default(string);
		}

		/// <summary>
		/// Return the value of the specified field.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>The System.Object which will contain the field value upon return.</returns>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"></see>.</exception>
		public object GetValue(int i)
		{
			this.ValidateRow();
			this.ValidateOridinal(i);
			return this.currentRow.GetValue(i);
		}

		/// <summary>
		/// Populates an array of objects with the field values of the current record.
		/// </summary>
		/// <param name="values">An array of System.Object to copy the attribute fields into.</param>
		/// <returns>The number of instances of System.Object in the array.</returns>
		public int GetValues(object[] values)
		{
			if (values == null)
				throw new ArgumentNullException("values");
			this.ValidateRow();
			int count = (values.Length < this.FieldCount) ? values.Length : this.FieldCount;
			for (int i = 0; i < count; i++)
				values[i] = this.currentRow.GetValue(i);
			return count;
		}

		/// <summary>
		/// Return whether the specified field is set to null or DBNull.
		/// </summary>
		/// <param name="i">The zero-based field index.</param>
		/// <returns>true if the specified field is set to null; otherwise, false.</returns>
		/// <exception cref="System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="HSS.Data.DataRowReader.FieldCount"></see>.</exception>
		public bool IsDBNull(int i)
		{
			object value = this.GetValue(i);
			return (DBNull.Value == value || null == value);
		}

		#region Not implemented
		/// <summary>
		/// Not implemented
		/// </summary>
		/// <param name="i"></param>
		/// <returns></returns>
		[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
		public IDataReader GetData(int i)
		{
			throw new NotSupportedException();
		}
		/// <summary>
		/// Not implemented
		/// </summary>
		/// <param name="i"></param>
		/// <returns></returns>
		[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
		public char GetChar(int i)
		{
			throw new NotSupportedException();
		}
		#endregion

		#endregion

		#region Metadata
		/// <summary>
		/// Contains the information regarding the Columns or Fields for the current DataRowReader
		/// </summary>
		[Serializable]
		class Metadata
		{
			#region Constructor
			/// <summary>
			/// Constructor
			/// </summary>
			public Metadata()
			{
			}
			/// <summary>
			/// Constructor
			/// </summary>
			/// <param name="fieldIndex">The Ordinal position of the Field</param>
			/// <param name="fieldName">The Name of the Field</param>
			/// <param name="fieldType">The Type of the Field</param>
			/// <param name="fieldTypeName">The TypeName of the Field</param>
			public Metadata(int fieldIndex, string fieldName, Type fieldType, string fieldTypeName)
			{
				this.FieldIndex = fieldIndex;
				this.FieldName = fieldName;
				this.FieldType = fieldType;
				this.FielTypeName = fieldTypeName;
			}
			#endregion

			#region Properties
			/// <summary>
			/// Gets or sets the Ordinal position of the Field
			/// </summary>
			public int FieldIndex { get; set; }
			/// <summary>
			/// Gets or sets the Name of the Field
			/// </summary>
			public string FieldName { get; set; }
			/// <summary>
			/// Gets or sets the Type of the Field
			/// </summary>
			public Type FieldType { get; set; }
			/// <summary>
			/// Gets or sets the TypeName of the Field
			/// </summary>
			public string FielTypeName { get; set; }
			#endregion
		}
		#endregion
	}
	#endregion
}